
import pymysql
import requests
import subprocess
import threading
import time
from datetime import datetime
import os
import paramiko
import sys
from urllib import parse
import threading
import ast

# 网络容器节点创建
from main_create_wyx import create_satnet_list
from main_topo_manager import process_links

import newfind
from mysql_control import MYSQL
from config2 import HOSTS, ONOS_IP, MYSQL_HOST, BUSI_HOST, HOSTS_ROOT_PWD

host1_num = 50
host2_num = 100
host3_num = 150
host4_num = 200

yh_list_file = "yh_list.txt"


def execute_remote_command(hostname, username, password, command):
    # 创建 SSH 客户端
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    try:
        # 连接远程主机
        client.connect(hostname=hostname, username=username, password=password, banner_timeout=360, timeout=360,
                       auth_timeout=360)
        stdin, stdout, stderr = client.exec_command(command)

        # 输出命令执行结果
        result = stdout.read().decode().strip()
        error = stderr.read().decode().strip()

        return result, error

    finally:
        # 关闭 SSH 连接
        client.close()


def run_remote_script(hostname, path):
    # 创建SSH客户端
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 连接到远程主机
    client.connect(hostname=hostname, username='root', password=HOSTS_ROOT_PWD)

    # 执行shell命令
    stdin, stdout, stderr = client.exec_command(path)
    print('错误检测:')
    print(stderr.read().decode())
    client.close()

def writepathtodatabase(time, serviceid, route):
    conn = pymysql.connect(host=MYSQL_HOST, user='root', passwd='123456', port=3306, db='satcontrol', charset='utf8')
    cur = conn.cursor()
    table_name = 'routerpath'
    sql = 'insert into ' + table_name + ' (timestamp,service,path) values(%s,%s,%s)'
    cur.execute(sql,(time, serviceid, route))
    conn.commit()
    print('write routepath finished')
    cur.close()
    conn.close()

# 从onos获取路由路径
def getpathstr(onosip, srcovsnum, dstovsnum):
    # 构建了一个URL，指向ONOS的REST API中用于查询两个OVS之间路径的端点。
    # parse.quote()函数用于对srcovsnum和dstovsnum进行URL编码，以确保它们可以被安全地包含在URL中。
    url = "http://" + onosip + ":8181/onos/v1/paths/" + parse.quote(srcovsnum) + "/" + parse.quote(dstovsnum)
    # print(url)
    # 使用requests库的get方法发送HTTP GET请求到之前构建的URL。这里使用了基本认证，用户名为onos，密码为rocks。
    res = requests.get(url=url, auth=('onos', 'rocks'))
    if res.status_code != 200:
        print('get方法请求onos网页数据失败,程序终止!')
        sys.exit(0)
    # 将响应的文本（res.text）中的true和false字符串替换为Python中的布尔值True和False。
    pathstr = res.text.replace('true', 'True').replace('false', 'False')  # string
    pathdict = dict(eval(pathstr))  # dictionary
    # print(pathdict)
    print('returned pathdict')
    return pathdict


def num2ji(ovsnum):
    # "ovsnum"
    conn = pymysql.connect(host=MYSQL_HOST, user='root', passwd='123456', port=3306, db='satcontrol', charset='utf8')
    cur = conn.cursor()
    sql = 'select satellite from satcontrol.satellite_uri where URI= \'%s\'' % (ovsnum)
    cur.execute(sql)
    conn.commit()
    ji = cur.fetchall()[0][0]
    # print(ji)
    cur.close()
    conn.close()
    return ji


def ji2num(ji):
    # 在数据库中查找对应的ID号,"'ji'"
    conn = pymysql.connect(host=MYSQL_HOST, user='root', passwd='123456', port=3306, db='satcontrol', charset='utf8')
    cur = conn.cursor()
    sql = 'select URI from satcontrol.satellite_uri where satellite=\'%s\'' % (ji)
    cur.execute(sql)
    conn.commit()
    ovsnum = cur.fetchall()[0][0]
    cur.close()
    conn.close()
    return ovsnum

# 从onos获取路径后的格式化处理
def str2routepath(pathstrs, srcyh, dstyh):
    print('start str2routepath......')
    routepathji = ''
    ovsji = pathstrs["paths"][0]["links"][0]["src"]["device"]
    for pathstr in pathstrs["paths"][0]["links"]:
        routepathid = pathstr["dst"]["device"]
        routepathji += num2ji(routepathid) + '->'
    ovsji = num2ji(ovsji)
    routepathji = ovsji + '->' + routepathji
    routepathji = routepathji.rstrip('->')
    print('routepathji: ',routepathji)
    print('finished str2routepath')
    return routepathji


# 将格式化处理后的路径写入mysql中
def routepathconfig(srcyh, dstyh, srcovsnum, dstovsnum):
    pathstrs = getpathstr(ONOS_IP, srcovsnum, dstovsnum)
    # print(pathstrs)
    routepath = str2routepath(pathstrs, srcyh, dstyh)
    print(routepath)
    time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    writepathtodatabase(time, '目标跟踪', routepath)
    # write panth to database

# 获取容器对应的进程ID(PID)
def get_namespace_pid(hostname, username, password, container_name):
    command = f'docker inspect --format="{{{{.State.Pid}}}}" {container_name}'
    print(hostname)
    print(command)
    result, error = execute_remote_command(hostname, username, password, command)
    if error:
        print(f"Error: {error}")
        return None
    return result.strip()

# 辅助判断两个容器是否分布在同一台主机中
def docker2host(docker_name):
    if docker_name.startswith('satellite_'):
        docker_number =  int(docker_name.split('_')[1])

        if(docker_number<host1_num):
            return 'HOST1'
        elif(docker_number<host2_num):
            return 'HOST2'
        elif(docker_number<host3_num):
            return 'HOST3'
        elif(docker_number<host4_num):
            return 'HOST4'

    return None

def docker2host_ip(docker_name):
    if docker_name.startswith('satellite_'):
        docker_number =  int(docker_name.split('_')[1])

        if(docker_number<host1_num):
            return HOSTS[1]
        elif(docker_number<host2_num):
            return HOSTS[1]
        elif(docker_number<host3_num):
            return HOSTS[2]
        elif(docker_number<host4_num):
            return HOSTS[3]

    return None


def create_yh_host_link(container_name1, veth_name1, container_name2, veth_name2, yhip, vxlanid,container_number1,container_number2):
    bridge_name = "br_sat"

    vxlan_id = vxlanid

    # if container_name1.startswith('yh'):
    #     container_number1 = int(container_name1.split('_')[1])
    # else:
    #     print(f"container name should be yh_xxx")
    #     return

    # if container_name2.startswith('Satellite'):
    #     container_number2 = int(container_name2.split('_')[1])
    # else:
    #     print(f"container name should be satellite_xxx")
    #     return

    if(container_number1<host1_num):
        host1 = HOSTS[2] 
    elif(container_number1<host2_num):
        host1 = HOSTS[1]
    elif(container_number1<host3_num):
        host1 = HOSTS[2]
    elif(container_number1<host4_num):
        host1 = HOSTS[3]

    hostname1 = host1['ip']
    username1 = host1['username']
    password1 = host1['password']
    port1 = host1['port']

    if(container_number2<host1_num):
        host2 = HOSTS[1]
    elif(container_number2<host2_num):
        host2 = HOSTS[1]
    elif(container_number2<host3_num):
        host2 = HOSTS[2]
    elif(container_number2<host4_num):
        host2 = HOSTS[3]

    hostname2 = host2['ip']
    username2 = host2['username']
    password2 = host2['password']
    port2 = host2['port']

    nspid1 = get_namespace_pid(hostname1, username1, password1, container_name1)
    nspid2 = get_namespace_pid(hostname2, username2, password2, container_name2)
    if nspid1 is None or nspid2 is None:
        return

    commands1 = [
        f"ip link add vlan_{vxlan_id} type vxlan id {vxlan_id} dstport 4789 remote {hostname2} local {hostname1} dev {port1}",
        f"ip link set vlan_{vxlan_id} up",
        # 将vxlan设备转移到docker命名空间
        f"ip link set dev vlan_{vxlan_id} name {veth_name1} netns {nspid1}",
        # 在docker命名空间中启动被重命名的设备
        f"ip netns exec {nspid1} ip link set dev {veth_name1} up",
        f'docker exec -d {container_name1} sh -c "ip addr add {yhip}/24 dev {veth_name1}"'
        # TODO:2024.9.14注释
        # f'docker exec -d {container_name1} sh -c "route add default gw {yhip} {veth_name1}"'
    ]

    commands2 = [
        f"ip link add vlan_{vxlan_id} type vxlan id {vxlan_id} dstport 4789 remote {hostname1} local {hostname2} dev {port2}",
        f"ip link set vlan_{vxlan_id} up",
        # 将vxlan设备转移到docker命名空间
        f"ip link set dev vlan_{vxlan_id} name {veth_name2} netns {nspid2}",
        # 在docker命名空间中启动被重命名的设备
        f"ip netns exec {nspid2} ip link set dev {veth_name2} up",
        # 将docker内网口绑定到网桥上
        f'docker exec -d "{container_name2}" sh -c "ovs-vsctl add-port {bridge_name} {veth_name2}" ',
    ]

    print(f"Creating inter host link between {container_name1} <--> {container_name2}:")
    for command in commands1:
        print(f"    Executing command: {command}")
        result, error = execute_remote_command(hostname1, username1, password1, command)
        print(f"    Result: {result}")
        if error:
            print(f" {command}   Error: {error}")

    for command in commands2:
        print(f"    Executing command: {command}")
        result, error = execute_remote_command(hostname2, username2, password2, command)
        print(f"    Result: {result}")
        if error:
            print(f"  {command}  Error: {error}")


def destroy_all_container_yh():
    command = f'docker rm -f $(docker ps -aq --filter "name=^yh")'

    print("destroy_all_container!!!")
    for host in HOSTS:
        host_name = host['name']
        host_ip = host['ip']
        username = host['username']
        password = host['password']

        error = execute_remote_command(host_ip, username, password, command)

        if error:
            print(f"{error}")
        else:
            print(f"container in {host_name} {host_ip} are all deleted ")
    return

def create_yh_vxlan(yh,yhip,ethnum1,ethnum2,satelliteName,yh1ip,vxlanstartid,container_number1,container_number2):

    basicPath = '/home/dg1/Desktop'
    path = basicPath + '/create_yh.sh ' + yh

    print('创建源端容器')
    run_remote_script(yhip, path)

    print('创建源端到卫星的vxlan链路')
    create_yh_host_link(yh, ethnum1, satelliteName, ethnum2, yh1ip, vxlanstartid,container_number1,container_number2)

def count_unique_sats(sats):
    unique_sats = set()  # 使用集合来存储唯一的satID

    for pair in sats:
        unique_sats.update(pair)  # 将每对中的satID添加到集合中

    unique_sats = list(unique_sats)  # 将集合转换为列表

    return unique_sats,len(unique_sats)  # 返回唯一satID的数量

if __name__ == "__main__":

    while(True):
        
        conn = pymysql.connect(host=MYSQL_HOST, user='root', passwd='123456', port=3306, db='satcontrol', charset='utf8')
        cur = conn.cursor()

        print("***************************************************")
        print("等待仿真启动......")

        # 循环等待
        while True:
            sql = 'select * from satcontrol.simflag where id = 1'
            cur.execute(sql)
            conn.commit()
            flag_set = cur.fetchall()
            startflag = flag_set[0][2]
            if startflag == 1:
                break
            time.sleep(1)

        print("仿真启动......")

        create_scenario_flag = flag_set[0][3]
        service_run_flag     = flag_set[0][4]
        
        # 从scenarioconfig表中获取创建场景时的数据
        sql = 'select * from satcontrol.scenarioconfig where id = 1'
        cur.execute(sql)    
        conn.commit()

        command = cur.fetchone()

        if command:
            image = command[1]       
            satellitenum = int(command[2])
            placeNum = int(command[3])
            # sats = command[4].split(',')
            places = command[5].split(',')

        sats = []

        # 本次仿真场景是否需要创建
        if create_scenario_flag == 1:

            print("开始创建场景...")

            print(f"placeNum:{placeNum}\n satelliteNum:{satellitenum}\n satelites:{sats}\n places:{places}")

            for i in range(1000):
                sats.append('Satellite'+str(i))


            # 创建卫星网络，此时不更新拓扑
            # 在实际有业务传输时才动态更新以节省资源
            create_satnet_list(sats,satellitenum)

            # print("开始处理网络连接...")
            # process_thread = threading.Thread(target=process_links, args=(sats,))
            # process_thread.start()
            # # process_links(sats)
            
            
            # satlinks = [
            #     ['sat1', 'sat2'],
            #     ['sat3', 'sat4'],
            #     ['sat2', 'sat3'],
            #     ['sat4', 'sat5'],
            #     ['sat1', 'sat5'],
            #     ['sat4', 'sat1'],
            #     ['sat6', 'sat1']
            # ]
            # unique_sats,satellitenunum = count_unique_sats(satlinks)
            
            # print(f"unique_sats:{unique_sats},satellitenunum:{satellitenunum}")
            # create_satnet_list(unique_sats,satellitenum,satlinks)

            print("场景创建完成...")



        # 本次仿真是否需要运行服务
        # TODO 之后需要根据他们的需求修改配置页面的参数，然后修改此处代码
        if service_run_flag == 1:

            print("开始运行业务程序...")


            sql = 'select * from satcontrol.userconfig where id=1'
            cur.execute(sql)    
            conn.commit()

            command = cur.fetchone()

            if command:
                service_type = command[1]

            # 业务类型：目标跟踪+星上协同计算
            if service_type == 'object-track':
                cutindex = command[2]  
                mainsat = command[3]
                sats_list = command[4].split(',')   

                print(f"service_type:{service_type} \n cutindex:{cutindex} \n mainsat:{mainsat}\n sats_list:{sats_list}")


                basicPath = '/home/dg1/Desktop'

                # vxlan设备的初始id            
                vxlanstartid = 10
                # 业务宿主机ip(暂时选用223)
                yhip = BUSI_HOST
                # 业务宿主机索引
                mainidex = int(mainsat.split('Satellite')[1])
                # 业务宿主机名称
                yhmain = f'yh_{mainidex}'
                # 业务宿主机对应的卫星名称
                satelliteMain = f'Satellite{mainidex}'

                # 卫星容器的宿主机ip
                IP = '192.168.210.222'
                ethnum1 = 'eth100'
                ethnum2 = 'eth101'

                # 上一次仿真待删除的容器列表
                # yhs_del = ['yh_113', 'yh_153', 'yh_131', 'yh_163', 'yh_122']

                with open(yh_list_file, 'r') as f:
                    yhs_del = ast.literal_eval(f.read().strip())  # 使用 ast.literal_eval 解析为列表
                
                print(f"yhs_del:{yhs_del}")
 
        

                satelliteNames_del = []
                vxlanid_del = []

                # vxlanid名称
                vxlanid = 'eth101'

                for yh in yhs_del:
                    yhidex = int(yh.split('_')[1])
                    vxlanid_del.append(f'vlan_{yhidex}')
                    satelliteNames_del.append(f'Satellite{yhidex}')


                # for yh in yhs_del:
                #     yhidex = int(yh.split('_')[1])
                #     satelliteNames_del.append(f'Satellite{yhidex}')
                    

                for yh,satellite,vxlanid in zip(yhs_del,satelliteNames_del,vxlanid_del):
                    print("IP:",IP)
                    path = f'docker exec -d {satellite} sh -c "ip link del {ethnum2}"'
                    print('删除原来目的端与用户间的vxlan:',path)
                    run_remote_script(IP, path)

                # 删除所有yh的容器
                destroy_all_container_yh()


                # 创建业务宿主机和vxlan--------------------------------------------------
                ## 使用的卫星集合
                yhs = []
                # 业务宿主机的名称
                for sat in sats_list:
                    yhs.append(f'yh_{int(sat.split("Satellite")[1])}')
                
                print(f"yhs:{yhs}")

  


                # 业务宿主机对应的卫星名称
                satelliteNames = sats_list

                yhips = []  
                eths = []
                vxlansetid = []
                

                # 新开一个线程执行拓扑管理(动态更新)，不阻塞主线程运行
                print("线程启动：处理网络连接......")
                process_thread = threading.Thread(target=process_links, args=(sats,satelliteMain, satelliteNames))
                process_thread.start()
                # process_links(sats)

                ipcount = 1
                for yh in yhs:
                    yhidex = int(yh.split('_')[1])
                    vxlansetid.append(yhidex)
                    yhips.append(f'10.0.1.{ipcount}')
                    ipcount += 1

                    
                for yh,satellite,yh1ip,vxlanid in zip(yhs,satelliteNames,yhips,vxlansetid):
                    print(f"yh:{yh},satellite:{satellite},yh1ip:{yh1ip},vxlanstartid:{vxlanid}")
                    container_number1 = yhs.index(yh)
                    container_number2 = satelliteNames.index(satellite)
                    print(f"container_number1:{container_number1},container_number2:{container_number2}")

                    # 创建对应的yh容器和vxlan设备，联通同一个卫星节点的yh容器和交换机容器
                    # create_yh_vxlan(yh,yhip,ethnum1,ethnum2,satellite,yh1ip,vxlanid,container_number1,container_number2)



                ## 测试联通性
                for yh2ip in yhips:
                    path = f'docker exec -d {yhmain} sh -c "ping {yh2ip} -c 4"'
                    time.sleep(2)
                    print('测试联通性:',path)
                    # run_remote_script(yhip, path)


                # 在数据库中创建satellite对应uri-------------------------------------------------------
                command2 = f'python3 /home/dg2/Desktop/satellite_uri.py -o {ONOS_IP}'  # 设置网桥 br_sat 控制器接口
                host_satellite = HOSTS[1]
                hostname = host_satellite['ip']
                username = host_satellite['username']
                password = host_satellite['password']
                # execute_remote_command(hostname, username, password, command2)


                #打印路由信息---------------------------------------
                # realcurrtime = datetime.now().timestamp()
                # for satelliteName in satelliteNames:
                #     print("打印路由信息")
                #     if satelliteName!=satelliteMain:
                #         srcovsnum = ji2num(satelliteName)
                #         dstovsnum = ji2num(satelliteMain)
                #         idx = satelliteName.split('Satellite')[1]
                #         yh1  = f'yh_{idx}'
                #         print("satelliteName:",satelliteName)
                #         print("yh1:",yh1)
                #         routepathconfig(yh1, yhmain, srcovsnum, dstovsnum)


                #  根据命令，执行对应业务
                # time.sleep(5)
                # count = 1
                # if service_type == 'object-track':
                #     print("开始执行object-track业务")
                #     command = f'docker exec -d {yhmain} sh -c "python3 /app/objtracker.py"'
                #     print("command:",command)
                #     run_remote_script(yhip, command)
                #     time.sleep(5)
                #     for yh in yhs:
                #         if yh != yhmain:
                #             command = f'docker exec -d {yh} sh -c "python3 /app/objdetector_tcp.py --video /app/input/video.avi --index {count} --grid_size 2x2"'
                #             print("command:",command)
                #             run_remote_script(yhip, command)
                #             count += 1


                # 记录上一次的业务宿主机列表
                # with open(yh_list_file, 'w') as f:
                #     f.write(str(yhs))  # 更新 link_list 的内容


                # 设置数据库中标识符为0
                sql = """
                        UPDATE simflag
                        SET startflag = 0,
                        servicerunflag = 0
                        WHERE id=1
                        """

                cur.execute(sql)
                conn.commit()

        # 无论是那种情况，都再次将数据库中标识符设置为0
        sql = """
                UPDATE simflag
                SET startflag = 0,
                createscenarioflag = 0
                WHERE id=1
                """

        cur.execute(sql)
        conn.commit()


